package agl

import (
	"agl/pkg/ast"
	"agl/pkg/parser"
	"agl/pkg/token"
	"agl/pkg/types"
	"agl/pkg/utils"
	"embed"
	_ "embed"
	"fmt"
	goast "go/ast"
	goparser "go/parser"
	gotoken "go/token"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync/atomic"
)

//go:embed pkgs/* core/*
var ContentFs embed.FS

var envIDCounter atomic.Int64

type Env struct {
	ID            int64
	structCounter atomic.Int64
	lookupTable   map[string]*Info  // Store constants/variables/functions
	lspTable      map[NodeKey]*Info // Store type for Expr/Stmt
	parent        *Env
	NoIdxUnwrap   bool
	fset          *token.FileSet
	pkgName       string
	inferrer      InferrerErrorCollector // Optional: for collecting errors instead of panicking
}

type InferrerErrorCollector interface {
	AddError(n ast.Node, msg string)
}

func (e *Env) WithPkgName(pkgName string, clb func()) {
	prev := e.pkgName
	e.pkgName = pkgName
	clb()
	e.pkgName = prev
}

func (e *Env) withEnv(clb func(*Env)) {
	clb(e.SubEnv())
}

type Info struct {
	Message     string
	Definition  token.Pos
	Definition1 DefinitionProvider
	Type        types.Type
}

func (i *Info) GetType() types.Type {
	if i != nil {
		return i.Type
	}
	return nil
}

func getGoRecv(e goast.Expr) string {
	recvTyp := e
	switch v := recvTyp.(type) {
	case *goast.Ident:
		if v.IsExported() {
			return v.Name
		}
	case *goast.StarExpr:
		return getGoRecv(v.X)
	case *goast.IndexExpr:
		return getGoRecv(v.X)
	case *goast.IndexListExpr:
		return getGoRecv(v.X)
	default:
		panic(fmt.Sprintf("%v", to(recvTyp)))
	}
	return ""
}

func goFuncDeclTypeToFuncType(name, pkgName string, expr *goast.FuncDecl, env *Env, keepRaw bool) types.FuncType {
	fT := goFuncTypeToFuncType(name, pkgName, expr.Type, env, keepRaw)
	var recvT []types.Type
	if expr.Recv != nil {
		for _, recv := range expr.Recv.List {
			for _, recvName := range recv.Names {
				recvTyp := recv.Type
				n := getGoRecv(recv.Type)
				if n == "" {
					continue
				}
				noop(recvName)
				t := env.GetGoType2(pkgName, recvTyp, keepRaw)
				recvT = append(recvT, t)
			}
		}
	}
	fT.Recv = recvT
	fT.Name = expr.Name.Name
	return fT
}

func goFuncTypeToFuncType(name, pkgName string, expr *goast.FuncType, env *Env, keepRaw bool) types.FuncType {
	native := utils.True()
	var paramsT []types.Type
	if expr.TypeParams != nil {
		for _, typeParam := range expr.TypeParams.List {
			for _, typeParamName := range typeParam.Names {
				typeParamType := typeParam.Type
				t := env.GetGoType2(pkgName, typeParamType, keepRaw)
				t = types.GenericType{W: t, Name: typeParamName.Name, IsType: true}
				env.Define(nil, typeParamName.Name, t)
				paramsT = append(paramsT, t)
			}
		}
	}
	var params []types.Type
	if expr.Params != nil {
		for _, param := range expr.Params.List {
			t := env.GetGoType2(pkgName, param.Type, keepRaw)
			n := max(len(param.Names), 1)
			for i := 0; i < n; i++ {
				params = append(params, t)
			}
		}
	}
	var result types.Type
	var results []types.Type
	if expr.Results != nil {
		for _, resultEl := range expr.Results.List {
			results = append(results, env.GetGoType2(pkgName, resultEl, keepRaw))
		}
	}
	if len(results) > 0 {
		lastResult := results[len(results)-1]
		lastResult = types.Unwrap(lastResult)
		if v, ok := lastResult.(types.InterfaceType); ok && v.Name == "error" {
			results = results[:len(results)-1]
			var inner types.Type = types.TupleType{Elts: results, KeepRaw: keepRaw}
			if len(results) == 0 {
				inner = types.VoidType{}
			} else if len(results) == 1 {
				inner = results[0]
			}
			result = types.ResultType{W: inner, Native: native, KeepRaw: keepRaw}
		} else if len(results) == 1 {
			result = results[0]
		} else {
			result = types.TupleType{Elts: results, KeepRaw: keepRaw}
		}
	} else {
		result = types.VoidType{}
	}
	parts := strings.Split(name, ".")
	name = parts[len(parts)-1]
	ft := types.FuncType{
		Name:       name,
		TypeParams: paramsT,
		Params:     params,
		Return:     result,
		IsNative:   native,
	}
	return ft
}

func funcDeclTypeToFuncType(name string, expr *ast.FuncDecl, env *Env, fset *token.FileSet, native bool) types.FuncType {
	fT := funcTypeToFuncType(name, expr.Type, env, fset, native)
	var recvT []types.Type
	if expr.Recv != nil {
		for _, recv := range expr.Recv.List {
			for _, n := range recv.Names {
				recvTyp := recv.Type
				t := env.GetType2(recvTyp, fset)
				if n.Mutable.IsValid() {
					t = types.MutType{W: t}
				}
				recvT = append(recvT, t)
			}
		}
	}
	fT.Recv = recvT
	fT.Name = expr.Name.Name
	return fT
}

func funcTypeToFuncType(name string, expr *ast.FuncType, env *Env, fset *token.FileSet, native bool) types.FuncType {
	var paramsT []types.Type
	if expr.TypeParams != nil {
		for _, typeParam := range expr.TypeParams.List {
			for _, typeParamName := range typeParam.Names {
				typeParamType := typeParam.Type
				t := env.GetType2(typeParamType, fset)
				t = types.GenericType{W: t, Name: typeParamName.Name, IsType: true}
				env.Define(typeParamName, typeParamName.Name, t)
				paramsT = append(paramsT, t)
			}
		}
	}
	var params []types.Type
	if expr.Params != nil {
		for _, param := range expr.Params.List {
			t := env.GetType2(param.Type, fset)
			n := max(len(param.Names), 1)
			for i := 0; i < n; i++ {
				tt := t
				if len(param.Names) > i && param.Names[i].Mutable.IsValid() {
					tt = types.MutType{W: tt}
				}
				params = append(params, tt)
			}
		}
	}
	var result types.Type
	if expr.Result != nil {
		result = env.GetType2(expr.Result, fset)
		if result == nil {
			panic(fmt.Sprintf("%s: type not found %v %v", fset.Position(expr.Pos()), expr.Result, to(expr.Result)))
		}
		if t, ok := result.(types.ResultType); ok {
			t.Native = native
			result = t
		} else if t1, ok := result.(types.OptionType); ok {
			t1.Native = native
			result = t1
		}
	}
	parts := strings.Split(name, ".")
	name = parts[len(parts)-1]
	if result == nil {
		switch expr.Result.(type) {
		case *ast.ResultExpr:
			result = types.ResultType{W: types.VoidType{}}
		case *ast.OptionExpr:
			result = types.OptionType{W: types.VoidType{}}
		default:
			result = types.VoidType{}
		}
	}
	ft := types.FuncType{
		Name:       name,
		TypeParams: paramsT,
		Params:     params,
		Return:     result,
		IsNative:   native,
	}
	return ft
}

func parseFuncTypeFromString(name, s string, env *Env, fset *token.FileSet) types.FuncType {
	return parseFuncTypeFromStringHelper(name, s, env, fset, false)
}

func parseFuncTypeFromStringNative(name, s string, env *Env, fset *token.FileSet) types.FuncType {
	return parseFuncTypeFromStringHelper(name, s, env, fset, true)
}

func parseFuncTypeFromStringHelper(name, s string, env *Env, fset *token.FileSet, native bool) types.FuncType {
	env = env.SubEnv()
	e := Must(parser.ParseExpr(s))
	expr := e.(*ast.FuncType)
	return funcTypeToFuncType(name, expr, env, fset, native)
}

func parseFuncDeclFromStringHelper(name, s string, env *Env, fset *token.FileSet) types.FuncType {
	s = "package main\n" + s
	env = env.SubEnv()
	e := Must(parser.ParseFile(token.NewFileSet(), "", s, parser.AllErrors))
	expr := e.Decls[0].(*ast.FuncDecl)
	return funcDeclTypeToFuncType(name, expr, env, fset, true)
}

func (e *Env) loadCoreTypes() {
	// Number types
	e.Define(nil, "int", types.TypeType{W: types.IntType{}})
	e.Define(nil, "uint", types.TypeType{W: types.UintType{}})
	e.Define(nil, "i8", types.TypeType{W: types.I8Type{}})
	e.Define(nil, "i16", types.TypeType{W: types.I16Type{}})
	e.Define(nil, "i32", types.TypeType{W: types.I32Type{}})
	e.Define(nil, "i64", types.TypeType{W: types.I64Type{}})
	e.Define(nil, "u8", types.TypeType{W: types.U8Type{}})
	e.Define(nil, "u16", types.TypeType{W: types.U16Type{}})
	e.Define(nil, "u32", types.TypeType{W: types.U32Type{}})
	e.Define(nil, "u64", types.TypeType{W: types.U64Type{}})
	e.Define(nil, "f32", types.TypeType{W: types.F32Type{}})
	e.Define(nil, "f64", types.TypeType{W: types.F64Type{}})

	// Aliases should be avoided
	e.Define(nil, "int8", types.TypeType{W: types.I8Type{}})
	e.Define(nil, "int16", types.TypeType{W: types.I16Type{}})
	e.Define(nil, "int32", types.TypeType{W: types.I32Type{}})
	e.Define(nil, "int64", types.TypeType{W: types.I64Type{}})
	e.Define(nil, "uint8", types.TypeType{W: types.U8Type{}})
	e.Define(nil, "uint16", types.TypeType{W: types.U16Type{}})
	e.Define(nil, "uint32", types.TypeType{W: types.U32Type{}})
	e.Define(nil, "uint64", types.TypeType{W: types.U64Type{}})
	e.Define(nil, "float32", types.TypeType{W: types.F32Type{}})
	e.Define(nil, "float64", types.TypeType{W: types.F64Type{}})

	e.Define(nil, "uintptr", types.TypeType{W: types.UintptrType{}})
	e.Define(nil, "complex128", types.TypeType{W: types.Complex128Type{}})
	e.Define(nil, "complex64", types.TypeType{W: types.Complex64Type{}})
	e.Define(nil, "void", types.TypeType{W: types.VoidType{}})
	e.Define(nil, "any", types.TypeType{W: types.AnyType{}})
	e.Define(nil, "bool", types.TypeType{W: types.BoolType{}})
	e.Define(nil, "string", types.TypeType{W: types.StringType{}})
	e.Define(nil, "byte", types.TypeType{W: types.ByteType{}})
	e.Define(nil, "rune", types.TypeType{W: types.RuneType{}})
	e.Define(nil, "error", types.TypeType{W: types.InterfaceType{Name: "error"}})
	e.Define(nil, "nil", types.TypeType{W: types.NilType{}})
	e.Define(nil, "true", types.BoolType{})
	e.Define(nil, "false", types.BoolType{})

	e.Define(nil, "@LINE", types.StringType{})
	e.Define(nil, "@COLUMN", types.StringType{})
}

func (e *Env) loadCoreFunctions(m *PkgVisited) {
	e.withEnv(func(nenv *Env) {
		_ = e.loadPkgAglStd(0, nil, nenv, "agl/cmp", "", m)
		e.Define(nil, "comparable", types.InterfaceType{Pkg: "", Name: "comparable"})
		e.Define(nil, "Type", types.InterfaceType{Pkg: "", Name: "Type"})
		e.Define(nil, "Number", types.InterfaceType{Pkg: "agl", Name: "Number"})
		e.Define(nil, "Integer", types.InterfaceType{Pkg: "agl", Name: "Integer"})
		e.DefineFn(nenv, "print", "func (args ...any)")
		e.DefineFn(nenv, "println", "func (args ...any)")
		e.DefineFn(nenv, "printf", "func (format string, a ...any)")
		e.DefineFn(nenv, "precondition", "func (pred bool, msg ...string)")
		e.DefineFn(nenv, "assert", "func (pred bool, msg ...string)")
		e.DefineFn(nenv, "assertEq", "func [T comparable](left, right T, msg ...string)")
		e.DefineFn(nenv, "make", "func[T, U any](t T, size ...U) T")
		e.DefineFn(nenv, "delete", "func[K comparable, V any](m map[K]V, k K)")
		e.DefineFn(nenv, "recover", "func () any")
		e.DefineFn(nenv, "len", "func [T any](v T) int")
		e.DefineFn(nenv, "cap", "func [T any](v T) int")
		e.DefineFn(nenv, "min", "func [T cmp.Ordered](x T, y ...T) T")
		e.DefineFn(nenv, "max", "func [T cmp.Ordered](x T, y ...T) T")
		e.DefineFn(nenv, "abs", "func [T Number](x T) T", WithDesc("Returns the absolute value of the given number."))
		e.DefineFn(nenv, "pow", "func [T, E Number](v T, e E) T")
		e.DefineFn(nenv, "zip", "func [T1, T2 any](a []T1, b []T2) [](T1, T2)")
		//e.DefineFn("clear", "func [T ~[]Type | ~map[Type]Type1](t T)")
		e.DefineFn(nenv, "append", "func [T any](slice []T, elems ...T) []T")
		e.DefineFn(nenv, "close", "func (c chan<- Type)")
		e.DefineFn(nenv, "panic", "func ()")
		e.DefineFn(nenv, "panicWith", "func (v any)")
		e.DefineFn(nenv, "new", "func [T any](T) *T")
	})
}

func loadAglImports(fileName string, depth int, t *TreeDrawer, env, nenv *Env, node *ast.File, m *PkgVisited, fset *token.FileSet) {
	var imports [][]string
	for _, d := range node.Imports {
		importName := ""
		if d.Name != nil {
			importName = d.Name.Name
		}
		path := strings.ReplaceAll(d.Path.Value, `"`, ``)
		imports = append(imports, []string{path, importName})
	}
	loadImports(fileName, depth, t, env, nenv, imports, m)
}

func loadGoImports(fileName string, depth int, t *TreeDrawer, env, nenv *Env, node *goast.File, m *PkgVisited) {
	var imports [][]string
	for _, d := range node.Imports {
		importName := ""
		if d.Name != nil {
			importName = d.Name.Name
		}
		path := strings.ReplaceAll(d.Path.Value, `"`, ``)
		imports = append(imports, []string{path, importName})
	}
	loadImports(fileName, depth, t, env, nenv, imports, m)
}

type TreeDrawer struct {
	rows []string
}

func (t *TreeDrawer) Add(depth int, lbl string) {
	t.rows = append(t.rows, fmt.Sprintf("%s├─ %s\n", strings.Repeat("│ ", depth), lbl))
}

func (t *TreeDrawer) Draw() {
	fmt.Print(strings.Join(t.rows, ""))
}

func loadImports(fileName string, depth int, t *TreeDrawer, env, nenv *Env, imports [][]string, m *PkgVisited) {
	if t != nil {
		t.Add(depth, fileName)
	}
	for _, d := range imports {
		path, importName := d[0], d[1]
		if t != nil {
			t.Add(depth+1, path)
		}
		if err := env.loadPkg(depth+2, t, nenv, path, importName, m); err != nil {
			panic(err)
		}
	}
}

func defineFromSrc(depth int, t *TreeDrawer, env, nenv *Env, path, pkgName string, src []byte, m *PkgVisited, fset *token.FileSet) {
	//fset := token.NewFileSet()
	node := Must(parser.ParseFile(fset, "", src, parser.AllErrors|parser.ParseComments))
	origPkgName := filepath.Base(path)
	pkgName = Or(pkgName, node.Name.Name)
	if err := env.DefinePkg(origPkgName, path); err != nil {
		panic(err)
	}
	if err := env.DefinePkg(pkgName, path); err != nil {
		panic(err)
	}
	nenv.WithPkgName(pkgName, func() {
		loadAglImports(path, depth, t, env, nenv, node, m, fset)
		loadDecls(env, nenv, node, path, pkgName, fset)
	})
}

func loadDecls(env, nenv *Env, node *ast.File, path, pkgName string, fset *token.FileSet) {
	for _, d := range node.Decls {
		switch decl := d.(type) {
		case *ast.FuncDecl:
			fullName := decl.Name.Name
			if decl.Recv != nil {
				t := decl.Recv.List[0].Type
				var recvName string
				switch v := t.(type) {
				case *ast.Ident:
					recvName = v.Name
				case *ast.StarExpr:
					switch vv := v.X.(type) {
					case *ast.Ident:
						recvName = vv.Name
					case *ast.SelectorExpr:
						recvName = vv.Sel.Name
					default:
						panic(fmt.Sprintf("%v", to(v.X)))
					}
				case *ast.SelectorExpr:
					recvName = v.Sel.Name
				default:
					panic(fmt.Sprintf("%v", to(t)))
				}
				fullName = recvName + "." + fullName
			}
			fullName = pkgName + "." + fullName
			ft := funcDeclTypeToFuncType("", decl, nenv, fset, true)
			if decl.Doc != nil && decl.Doc.List[0].Text == "// agl:wrapped" {
				ft.IsNative = false
				switch v := ft.Return.(type) {
				case types.ResultType:
					v.Native = false
					ft.Return = v
				case types.OptionType:
					v.Native = false
					ft.Return = v
				}
			} else {
				ft.IsNative = true
			}
			var opts []SetTypeOption
			if decl.Doc != nil {
				doc := decl.Doc.List[0].Text
				r := regexp.MustCompile(`// ([^:]+):(\d+):(\d+)$`)
				if r.MatchString(doc) {
					goroot := runtime.GOROOT()
					parts := strings.Split(doc, ":")
					absPath, _ := filepath.Abs(filepath.Join(goroot, "src", path))
					absPath = filepath.Join(absPath, strings.TrimPrefix(parts[0], "// "))
					line, _ := strconv.Atoi(parts[1])
					character, _ := strconv.Atoi(parts[2])
					opts = append(opts, WithDefinition1(DefinitionProvider{URI: absPath, Line: line, Character: character}))
				}
			}
			if err := env.DefineFnNative2(fullName, ft, opts...); err != nil {
				assert(false, err.Error())
			}
			// If this is an interface method, update the interface type to include this method
			// Only do this for interfaces (not structs)
			if decl.Recv != nil {
				t := decl.Recv.List[0].Type
				// Skip pointer receivers and non-identifier receivers (they're for structs)
				if ident, ok := t.(*ast.Ident); ok {
					recvName := ident.Name
					interfaceName := pkgName + "." + recvName
					if interfaceInfo := env.Get(interfaceName); interfaceInfo != nil {
						if interfaceT, ok := interfaceInfo.(types.InterfaceType); ok {
							// This is an interface method - add it to the interface
							methodName := decl.Name.Name
							ft.Name = methodName
							interfaceT.Methods = append(interfaceT.Methods, types.InterfaceMethod{Name: methodName, Typ: ft})
							// Update the interface in the environment
							env.Define(nil, interfaceName, interfaceT)
						}
					}
				}
			}
		case *ast.GenDecl:
			for _, s := range decl.Specs {
				switch spec := s.(type) {
				case *ast.TypeSpec:
					specName := pkgName + "." + spec.Name.Name
					switch v := spec.Type.(type) {
					case *ast.Ident:
						t := nenv.GetType2(v, fset)
						env.Define(nil, specName, types.CustomType{Pkg: pkgName, Name: spec.Name.Name, W: t})
					case *ast.MapType:
						t := nenv.GetType2(v, fset)
						env.Define(nil, specName, t)
					case *ast.StarExpr:
						t := nenv.GetType2(v, fset)
						env.Define(nil, specName, t)
					case *ast.ArrayType:
						t := nenv.GetType2(v, fset)
						env.Define(nil, specName, t)
					case *ast.FuncType:
						if spec.TypeParams != nil {
							for _, e := range spec.TypeParams.List {
								typ := nenv.GetType2(e.Type, fset)
								for _, n := range e.Names {
									nenv.Define(nil, n.Name, types.GenericType{Name: n.Name, W: typ})
								}
							}
						}
						t := nenv.GetType2(v, fset).(types.FuncType)
						t.Name = specName
						if spec.TypeParams != nil {
							for _, tp := range spec.TypeParams.List {
								tpT := nenv.GetType2(tp.Type, fset)
								for _, n := range tp.Names {
									t.TypeParams = append(t.TypeParams, types.GenericType{Name: n.Name, W: tpT})
								}
							}
						}
						env.Define(nil, specName, t)
					case *ast.InterfaceType:
						var methodsT []types.InterfaceMethod
						if v.Methods != nil {
							for _, m := range v.Methods.List {
								for _, n := range m.Names {
									fullName := pkgName + "." + spec.Name.Name + "." + n.Name
									t := nenv.GetType2(m.Type, fset)
									tmp := t.(types.FuncType)
									tmp.Name = n.Name
									env.Define(nil, fullName, tmp)
									methodsT = append(methodsT, types.InterfaceMethod{Name: n.Name, Typ: tmp})
								}
							}
						}
						env.Define(nil, specName, types.InterfaceType{Pkg: pkgName, Name: spec.Name.Name, Methods: methodsT})
					case *ast.StructType:
						env.Define(nil, specName, types.StructType{Pkg: pkgName, Name: spec.Name.Name})
						if v.Fields != nil {
							for _, field := range v.Fields.List {
								t := nenv.GetType2(field.Type, fset)
								for _, name := range field.Names {
									fieldName := pkgName + "." + spec.Name.Name + "." + name.Name
									switch vv := t.(type) {
									case types.InterfaceType:
										env.Define(nil, fieldName, types.InterfaceType{Pkg: vv.Pkg, Name: vv.Name, Methods: vv.Methods})
									case types.StructType:
										env.Define(nil, fieldName, types.StructType{Pkg: vv.Pkg, Name: vv.Name})
									case types.TypeType:
										env.Define(nil, fieldName, vv.W)
									case types.ArrayType:
										env.Define(nil, fieldName, vv)
									case types.StarType:
										env.Define(nil, fieldName, vv)
									case types.MapType:
										env.Define(nil, fieldName, vv)
									case types.CustomType:
										env.Define(nil, fieldName, vv)
									default:
										panic(fmt.Sprintf("%v", to(t)))
									}
								}
							}
						}
					default:
						panic(fmt.Sprintf("%v", to(spec.Type)))
					}
				case *ast.ImportSpec:
				case *ast.ValueSpec:
					for _, name := range spec.Names {
						fieldName := pkgName + "." + name.Name
						t := nenv.GetType2(spec.Type, fset)
						env.Define(nil, fieldName, t)
					}
				default:
					panic(fmt.Sprintf("%v", to(s)))
				}
			}
		}
	}
}

type Later struct {
	pkgName   string
	s         goast.Spec
	node      *goast.File
	fset      *gotoken.FileSet
	path      string
	entryName string
}

func defineStructsFromGoSrc(path string, depth int, t *TreeDrawer, files []EntryContent, env, nenv *Env, m *PkgVisited, keepRaw bool) {
	var tryLater []Later
	for _, entry := range files {
		env.withEnv(func(nenv *Env) {
			//p("LOADING", fullPath)
			fset := gotoken.NewFileSet()
			node := Must(goparser.ParseFile(fset, "", entry.Content, goparser.AllErrors|goparser.ParseComments))
			pkgName := node.Name.Name
			loadGoImports(filepath.Join(path, entry.Name), depth, t, env, nenv, node, m)
			for _, d := range node.Decls {
				switch decl := d.(type) {
				case *goast.GenDecl:
					for _, s := range decl.Specs {
						processSpec(path, entry.Name, node, fset, s, env, pkgName, &tryLater, keepRaw)
					}
				}
			}
		})
	}
	i := 0
	for len(tryLater) > 0 {
		var d Later
		d, tryLater = tryLater[0], tryLater[1:]
		processSpec(d.path, d.entryName, d.node, d.fset, d.s, env, d.pkgName, &tryLater, keepRaw)
		i++
		if i > 10 {
			break
		}
	}
}

func processSpec(path, entryName string, node *goast.File, fset *gotoken.FileSet, s goast.Spec, env *Env, pkgName string, tryLater *[]Later, keepRaw bool) {
	switch spec := s.(type) {
	//case *goast.ValueSpec:
	//	for _, name := range spec.Names {
	//		fieldName := pkgName + "." + name.Name
	//		t := env.GetGoType2(pkgName, spec.Type, keepRaw)
	//		p(fieldName, spec.Type, t)
	//		env.DefineForce(nil, fieldName, t)
	//	}
	case *goast.TypeSpec:
		if !spec.Name.IsExported() {
			return
		}
		specName := pkgName + "." + spec.Name.Name
		switch v := spec.Type.(type) {
		case *goast.StructType:
			var fields []types.FieldType
			if v.Fields != nil {
				for _, field := range v.Fields.List {
					tmp := func() types.Type {
						t := env.GetGoType2(pkgName, field.Type, keepRaw)
						t = types.Unwrap(t)
						if TryCast[types.VoidType](t) || t == nil {
							fT := field.Type
							if vv, ok := field.Type.(*goast.StarExpr); ok {
								fT = vv.X
							}
							if vv, ok := fT.(*goast.Ident); ok && vv.Name == spec.Name.Name {
								t = types.StructType{Pkg: pkgName, Name: spec.Name.Name}
							} else {
								*tryLater = append(*tryLater, Later{s: s, pkgName: pkgName, node: node, fset: fset, path: path, entryName: entryName})
								//p("DEFSTRUCT1", pkgName, spec.Name.Name)
								env.Define(nil, specName, types.StructType{Pkg: pkgName, Name: spec.Name.Name, Fields: fields})
								return nil
							}
						}
						return t
					}
					if len(field.Names) == 0 {
						fields = append(fields, types.FieldType{Name: "", Typ: tmp()})
					}
					for _, name := range field.Names {
						if !name.IsExported() {
							continue
						}
						fields = append(fields, types.FieldType{Name: name.Name, Typ: tmp()})
					}
				}
			}
			//p("DEFSTRUCT2", pkgName, spec.Name.Name)
			//p(path, entryName, node, fset)
			absPath, _ := filepath.Abs(filepath.Join(path, entryName))
			pos := fset.Position(spec.Pos())
			env.Define(nil, specName, types.StructType{Pkg: pkgName, Name: spec.Name.Name, Fields: fields},
				WithDefinition1(DefinitionProvider{URI: absPath, Line: pos.Line, Character: pos.Column}))
		case *goast.InterfaceType:
			var methodsT []types.InterfaceMethod
			if v.Methods != nil {
				for _, m := range v.Methods.List {
					for _, n := range m.Names {
						if !n.IsExported() {
							continue
						}
						fullName := pkgName + "." + spec.Name.Name + "." + n.Name
						t := env.GetGoType2(pkgName, m.Type, keepRaw)
						env.Define(nil, fullName, t)
						methodsT = append(methodsT, types.InterfaceMethod{Name: n.Name, Typ: t})
					}
				}
			}
			env.Define(nil, specName, types.InterfaceType{Pkg: pkgName, Name: spec.Name.Name, Methods: methodsT})
		case *goast.IndexListExpr:
		case *goast.ArrayType:
		case *goast.MapType:
		case *goast.Ident:
			t := env.GetGoType2(pkgName, v, keepRaw)
			if TryCast[types.VoidType](t) {
				if !v.IsExported() {
					return
				}
				*tryLater = append(*tryLater, Later{s: s, pkgName: pkgName})
				return
			}
			env.Define(nil, specName, t)
		case *goast.FuncType:
		case *goast.SelectorExpr:
		case *goast.IndexExpr:
		default:
			panic(fmt.Sprintf("%v", to(spec.Type)))
		}
	}
}

func defineFromGoSrc(env, nenv *Env, path string, src []byte, keepRaw bool) {
	node := Must(goparser.ParseFile(gotoken.NewFileSet(), "", src, goparser.AllErrors|goparser.ParseComments))
	pkgName := node.Name.Name
	_ = env.DefinePkg(pkgName, path) // Many files have the same "package"
	for _, d := range node.Decls {
		switch decl := d.(type) {
		case *goast.FuncDecl:
			if !decl.Name.IsExported() {
				continue
			}
			fnT := goFuncDeclTypeToFuncType("", pkgName, decl, env, keepRaw)
			fullName := decl.Name.Name
			//p(fnT, "  |  ", fullName)
			if decl.Recv != nil {
				recvName := getGoRecv(decl.Recv.List[0].Type)
				if recvName == "" {
					continue
				}
				fullName = recvName + "." + fullName
			}
			fullName = pkgName + "." + fullName
			if err := env.DefineFnNative2(fullName, fnT); err != nil {
				continue // TODO should not skip errors
			}
		}
	}
}

func (e *Env) loadPkg(depth int, t *TreeDrawer, nenv *Env, pkgPath, pkgName string, m *PkgVisited) error {
	pkgName = Or(pkgName, filepath.Base(pkgPath))
	if m.ContainsAdd(pkgPath + "_" + pkgName + "_" + strconv.FormatInt(e.ID, 10)) {
		return nil
	}
	//p("?LOADPKG", pkgPath, pkgName)
	if err := e.loadPkgLocal(depth, t, nenv, pkgPath, pkgName, m); err != nil {
		if err := e.loadPkgAglStd(depth, t, nenv, pkgPath, pkgName, m); err != nil {
			if err := e.loadPkgVendor(depth, t, nenv, pkgPath, pkgName, m); err != nil {
				return err
			}
		}
	}
	return nil
}

func (e *Env) loadPkgLocal(depth int, t *TreeDrawer, nenv *Env, pkgPath, pkgName string, m *PkgVisited) error {
	pkgFullPath := trimPrefixPath(pkgPath)
	if err := e.DefinePkg(pkgPath, pkgName); err != nil {
		return nil
	}
	if err := e.loadVendor2(depth, t, nenv, pkgFullPath, m); err != nil {
		return err
	}
	return nil
}

func (e *Env) loadPkgAglStd(depth int, t *TreeDrawer, nenv *Env, path, pkgName string, m *PkgVisited) error {
	var prefix string
	if strings.HasPrefix(path, "agl/") {
		prefix = "agl/"
	} else {
		prefix = "std/"
		if !strings.HasPrefix(path, "std/") {
			path = filepath.Join("std", path)
		}
	}
	//if path == "std/fmt" {
	//	goroot := runtime.GOROOT()
	//	absPath, _ := filepath.Abs(filepath.Join(goroot, "src", "fmt"))
	//	if err := e.loadVendor2(nenv, absPath, m); err != nil {
	//		return err
	//	}
	//}
	return e.loadAglFile(depth, t, nenv, prefix, path, pkgName, m)
}

func (e *Env) loadPkgVendor(depth int, t *TreeDrawer, nenv *Env, path, pkgName string, m *PkgVisited) error {
	vendorPath := filepath.Join("vendor", path)
	if err := e.loadVendor2(depth, t, nenv, vendorPath, m); err != nil {
		if err := e.loadAglFile(depth, t, nenv, "", path, pkgName, m); err != nil {
			return err
		}
	}
	return nil
}

func (e *Env) loadAglFile(depth int, t *TreeDrawer, nenv *Env, prefix, path, pkgName string, m *PkgVisited) error {
	stdFilePath := filepath.Join("pkgs", path, filepath.Base(path)+".agl")
	by, err := ContentFs.ReadFile(stdFilePath)
	if err != nil {
		return err
	}
	final := filepath.Dir(strings.TrimPrefix(stdFilePath, "pkgs/"+prefix))
	defineFromSrc(depth, t, e, nenv, final, pkgName, by, m, e.fset)
	return nil
}

func (e *Env) loadVendor2(depth int, t *TreeDrawer, nenv *Env, path string, m *PkgVisited) error {
	keepRaw := utils.True()
	files, err := readDir(path, m)
	if err != nil {
		return err
	}
	defineStructsFromGoSrc(path, depth, t, files, e, nenv, m, keepRaw)
	for _, entry := range files {
		defineFromGoSrc(e, nenv, path, entry.Content, keepRaw)
	}
	return nil
}

type EntryContent struct {
	Name    string
	Content []byte
}

// Read `*.go` files from a given directory
func readDir(path string, m *PkgVisited) ([]EntryContent, error) {
	entries, err := os.ReadDir(path)
	if err != nil {
		return nil, err
	}
	files := make([]EntryContent, 0)
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
			continue
		}
		fullPath := filepath.Join(path, entry.Name())
		if m.ContainsAdd(fullPath) {
			continue
		}
		by, err := os.ReadFile(fullPath)
		if err != nil {
			continue
		}
		files = append(files, EntryContent{Name: entry.Name(), Content: by})
	}
	return files, nil
}

func (e *Env) loadPkgAgl(m *PkgVisited) {
	_ = e.DefinePkg("agl", "agl")
	e.withEnv(func(nenv *Env) {
		_ = e.loadPkgAglStd(0, nil, nenv, "agl/cmp", "", m)
		_ = e.loadPkgAglStd(0, nil, nenv, "agl/iter", "", m)
		e.Define(nil, "Iterator", types.InterfaceType{
			Pkg:        "agl",
			Name:       "Iterator",
			TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}},
			Methods: []types.InterfaceMethod{
				{Name: "Any", Typ: types.FuncType{}},
				{Name: "AllSatisfy", Typ: types.FuncType{}},
				{Name: "Contains", Typ: types.FuncType{}},
				{Name: "ForEach", Typ: types.FuncType{}},
				{Name: "Filter", Typ: types.FuncType{}},
				{Name: "Map", Typ: types.FuncType{}},
			},
		})
		// Define Sequence[T] as a type alias struct that wraps iter.Seq[T]
		// This represents: type Sequence[T any] iter.Seq[T]
		// iter.Seq[T] is: func(yield func(T) bool)
		// We need to create this type with T as the generic parameter
		seqFuncType := types.FuncType{
			Params: []types.Type{
				types.FuncType{
					Params: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}},
					Return: types.BoolType{},
				},
			},
		}
		e.Define(nil, "Sequence", types.StructType{
			Pkg:        "",
			Name:       "Sequence",
			TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}, IsType: true}},
			Fields:     []types.FieldType{{Name: "__alias", Typ: seqFuncType}},
		})
		e.Define(nil, "DoubleEndedIterator", types.InterfaceType{Pkg: "agl", Name: "DoubleEndedIterator", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "CloneIterator", types.InterfaceType{Pkg: "agl", Name: "CloneIterator", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "DoubleEndedExactSizeIterator", types.InterfaceType{Pkg: "agl", Name: "DoubleEndedExactSizeIterator", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "DictEntry", types.StructType{Pkg: "", Name: "DictEntry", TypeParams: []types.Type{
			types.GenericType{Name: "K", W: types.AnyType{}},
			types.GenericType{Name: "V", W: types.AnyType{}},
		}})
		e.Define(nil, "DictEntry.Key", types.GenericType{Name: "K", W: types.AnyType{}})
		e.Define(nil, "DictEntry.Value", types.GenericType{Name: "V", W: types.AnyType{}})
		e.Define(nil, "Peekable", types.StructType{Pkg: "", Name: "Peekable", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "Peekable.Peek", types.FuncType{Return: types.OptionType{W: types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "Peekable.Next", types.FuncType{Return: types.OptionType{W: types.GenericType{Name: "T", W: types.AnyType{}}}})
		// Iterator methods on Peekable - these return Peekable to maintain peek capability
		e.Define(nil, "Peekable.Skip", types.FuncType{Params: []types.Type{types.IntType{}}, Return: types.StructType{Pkg: "", Name: "Peekable", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}}})
		e.Define(nil, "Peekable.Take", types.FuncType{Params: []types.Type{types.IntType{}}, Return: types.StructType{Pkg: "", Name: "Peekable", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}}})
		e.Define(nil, "Peekable.Filter", types.FuncType{Params: []types.Type{types.FuncType{Params: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}, Return: types.BoolType{}}}, Return: types.StructType{Pkg: "", Name: "Peekable", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}}})
		e.Define(nil, "IterVec", types.StructType{Pkg: "", Name: "IterVec", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "IterVec.Next", types.FuncType{Return: types.OptionType{W: types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "IterVec.NextBack", types.FuncType{Return: types.OptionType{W: types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "IterVec.Len", types.FuncType{Return: types.IntType{}})
		e.Define(nil, "IterVec.IsEmpty", types.FuncType{Return: types.BoolType{}})
		e.Define(nil, "IterVec.Clone", types.FuncType{Return: types.InterfaceType{Pkg: "agl", Name: "CloneIterator", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}}})
		e.Define(nil, "agl.Rev", types.StructType{Pkg: "agl", Name: "Rev", TypeParams: []types.Type{types.GenericType{Name: "T", W: types.AnyType{}}}})
		e.Define(nil, "agl.Set", types.SetType{K: types.GenericType{Name: "T", W: types.AnyType{}}})
		e.Define(nil, "agl.Vec", types.ArrayType{Elt: types.GenericType{Name: "T", W: types.AnyType{}}})
		e.Define(nil, "agl.Enum", types.EnumType{})
		e.Define(nil, "agl.String", types.StringType{})
		e.DefineFn(nenv, "agl.Enum.RawValue", "func [T any](e agl.Enum[T]) int")
		e.DefineFn(nenv, "agl.Set.Contains", "func [T comparable](s agl.Set[T], el T) bool")
		e.DefineFn(nenv, "agl.Set.ContainsWhere", "func [T comparable](s agl.Set[T], where: p func(T) bool) bool")
		e.DefineFn(nenv, "agl.Set.Equals", "func [T comparable](s, other agl.Set[T]) bool")
		e.DefineFn(nenv, "agl.Set.Filter", "func [T comparable](s agl.Set[T], f func(e T) bool) agl.Set[T]", WithDesc("Returns a new set containing the elements of the set that satisfy the given predicate."))
		e.DefineFn(nenv, "agl.Set.First", "func [T comparable](s agl.Set[T]) T?")
		e.DefineFn(nenv, "agl.Set.FirstWhere", "func [T comparable](s agl.Set[T], where: predicate func(T) bool) T?")
		e.DefineFn(nenv, "agl.Set.ForEach", "func [T comparable](s agl.Set[T], f func(e T))")
		e.DefineFn(nenv, "agl.Set.FormIntersection", "func [T comparable](mut s agl.Set[T], other Iterator[T])")
		e.DefineFn(nenv, "agl.Set.FormSymmetricDifference", "func [T comparable](mut s agl.Set[T], other Iterator[T])")
		e.DefineFn(nenv, "agl.Set.FormUnion", "func [T comparable](mut s agl.Set[T], other Iterator[T])")
		e.DefineFn(nenv, "agl.Set.Insert", "func [T comparable](mut s agl.Set[T], el T) bool", WithDesc("Inserts the given element in the set if it is not already present."))
		e.DefineFn(nenv, "agl.Set.Intersection", "func [T comparable](s, other agl.Set[T]) agl.Set[T]", WithDesc("Returns a new set with the elements that are common to both this set and the given sequence."))
		e.DefineFn(nenv, "agl.Set.Intersects", "func [T comparable](s agl.Set[T], other Iterator[T]) bool", WithDesc("Returns a Boolean value that indicates whether the set has members in common with the given sequence."))
		e.DefineFn(nenv, "agl.Set.IsDisjoint", "func [T comparable](s agl.Set[T], other Iterator[T]) bool", WithDesc("Returns a Boolean value that indicates whether the set has no members in common with the given sequence."))
		e.DefineFn(nenv, "agl.Set.IsEmpty", "func [T comparable](s agl.Set[T]) bool")
		e.DefineFn(nenv, "agl.Set.IsStrictSubset", "func [T comparable](s agl.Set[T], other Iterator[T]) bool")
		e.DefineFn(nenv, "agl.Set.IsStrictSuperset", "func [T comparable](s agl.Set[T], other Iterator[T]) bool")
		e.DefineFn(nenv, "agl.Set.IsSubset", "func [T comparable](s agl.Set[T], other Iterator[T]) bool")
		e.DefineFn(nenv, "agl.Set.IsSuperset", "func [T comparable](s agl.Set[T], other Iterator[T]) bool")
		e.DefineFn(nenv, "agl.Set.Iter", "func [T comparable](s agl.Set[T]) Iterator[T]")
		e.DefineFn(nenv, "agl.Set.Len", "func [T comparable](s agl.Set[T]) int", WithDesc("The number of elements in the set."))
		e.DefineFn(nenv, "agl.Set.Map", "func [T comparable, R any](s agl.Set[T], f func(T) R) []R")
		e.DefineFn(nenv, "agl.Set.Max", "func [T comparable](s agl.Set[T]) T?")
		e.DefineFn(nenv, "agl.Set.Min", "func [T comparable](s agl.Set[T]) T?")
		e.DefineFn(nenv, "agl.Set.Remove", "func [T comparable](mut s agl.Set[T], el T) T?")
		e.DefineFn(nenv, "agl.Set.RemoveFirst", "func [T comparable](mut s agl.Set[T]) T")
		e.DefineFn(nenv, "agl.Set.Subtract", "func [T comparable](mut s agl.Set[T], other Iterator[T])")
		e.DefineFn(nenv, "agl.Set.Subtracting", "func [T comparable](s agl.Set[T], other Iterator[T]) agl.Set[T]")
		e.DefineFn(nenv, "agl.Set.SymmetricDifference", "func [T comparable](s agl.Set[T], other Iterator[T]) agl.Set[T]")
		e.DefineFn(nenv, "agl.Set.Union", "func [T comparable](s agl.Set[T], other Iterator[T]) agl.Set[T]")
		e.DefineFn(nenv, "agl.String.AsBytes", "func (s string) []byte")
		e.DefineFn(nenv, "agl.String.Contains", "func (s, substr string) bool")
		e.DefineFn(nenv, "agl.String.ContainsAny", "func (s, chars string) bool")
		e.DefineFn(nenv, "agl.String.Count", "func (s, substr string) int")
		e.DefineFn(nenv, "agl.String.Cut", "func (s, sep string) (string, string)?")
		e.DefineFn(nenv, "agl.String.CutPrefix", "func (s, prefix string) string?")
		e.DefineFn(nenv, "agl.String.CutSuffix", "func (s, suffix string) string?")
		e.DefineFn(nenv, "agl.String.Enumerated", "func (s string) [](int, i32)")
		e.DefineFn(nenv, "agl.String.F32", "func (s string) f32?", WithDesc("Parse the string into an 'f32' value.\nReturns None if the string cannot be parsed as an 'f32'."))
		e.DefineFn(nenv, "agl.String.F64", "func (s string) f64?", WithDesc("Parse the string into an 'f64' value.\nReturns None if the string cannot be parsed as an 'f64'."))
		e.DefineFn(nenv, "agl.String.HasPrefix", "func (s, prefix string) bool")
		e.DefineFn(nenv, "agl.String.HasSuffix", "func (s, prefix string) bool")
		e.DefineFn(nenv, "agl.String.I16", "func (s string) i16?", WithDesc("Parse the string into an 'i16' value.\nReturns None if the string cannot be parsed as an 'i16'."))
		e.DefineFn(nenv, "agl.String.I32", "func (s string) i32?", WithDesc("Parse the string into an 'i32' value.\nReturns None if the string cannot be parsed as an 'i32'."))
		e.DefineFn(nenv, "agl.String.I64", "func (s string) i64?", WithDesc("Parse the string into an 'i64' value.\nReturns None if the string cannot be parsed as an 'i64'."))
		e.DefineFn(nenv, "agl.String.I8", "func (s string) i8?", WithDesc("Parse the string into an 'i8' value.\nReturns None if the string cannot be parsed as an 'i8'."))
		e.DefineFn(nenv, "agl.String.Index", "func (s, substr string) int?")
		e.DefineFn(nenv, "agl.String.Int", "func (s string) int?", WithDesc("Parse the string into an 'int' value.\nReturns None if the string cannot be parsed as an 'int'."))
		e.DefineFn(nenv, "agl.String.IsEmpty", "func (s string) bool")
		e.DefineFn(nenv, "agl.String.LastIndex", "func (s, substr string) int?")
		e.DefineFn(nenv, "agl.String.Len", "func (s string) int")
		e.DefineFn(nenv, "agl.String.Lines", "func (s string) []string")
		e.DefineFn(nenv, "agl.String.Lowercased", "func (s string) string")
		e.DefineFn(nenv, "agl.String.Repeat", "func (s string, count int) string")
		e.DefineFn(nenv, "agl.String.Replace", "func (s, old, new string, n int) string")
		e.DefineFn(nenv, "agl.String.ReplaceAll", "func (s, old, new string) string")
		e.DefineFn(nenv, "agl.String.Split", "func (s, sep string) []string", WithDesc("Split slices s into all substrings separated by sep and returns a slice of\nthe substrings between those separators."))
		e.DefineFn(nenv, "agl.String.SplitAfter", "func (s, sep string) []string")
		e.DefineFn(nenv, "agl.String.Trim", "func (s, cutset string) string")
		e.DefineFn(nenv, "agl.String.TrimLeft", "func (s, cutset string) string")
		e.DefineFn(nenv, "agl.String.TrimPrefix", "func (s, prefix string) string")
		e.DefineFn(nenv, "agl.String.TrimRight", "func (s, cutset string) string")
		e.DefineFn(nenv, "agl.String.TrimSpace", "func (s string) string")
		e.DefineFn(nenv, "agl.String.TrimSuffix", "func (s, suffix string) string")
		e.DefineFn(nenv, "agl.String.U16", "func (s string) u16?", WithDesc("Parse the string into an 'u16' value.\nReturns None if the string cannot be parsed as an 'u16'."))
		e.DefineFn(nenv, "agl.String.U32", "func (s string) u32?", WithDesc("Parse the string into an 'u32' value.\nReturns None if the string cannot be parsed as an 'u32'."))
		e.DefineFn(nenv, "agl.String.U64", "func (s string) u64?", WithDesc("Parse the string into an 'u64' value.\nReturns None if the string cannot be parsed as an 'u64'."))
		e.DefineFn(nenv, "agl.String.U8", "func (s string) u8?", WithDesc("Parse the string into an 'u8' value.\nReturns None if the string cannot be parsed as an 'u8'."))
		e.DefineFn(nenv, "agl.String.Uint", "func (s string) uint?", WithDesc("Parse the string into an 'uint' value.\nReturns None if the string cannot be parsed as an 'uint'."))
		e.DefineFn(nenv, "agl.String.Uppercased", "func (s string) string")
		e.DefineFn(nenv, "agl.String.ZFill", "func (s string, width int) string")
		e.DefineFn(nenv, "agl.Int.Sqrt", "func (int) int")
		e.DefineFn(nenv, "agl.I8.Sqrt", "func (i8) i8")
		e.DefineFn(nenv, "agl.I16.Sqrt", "func (i16) i16")
		e.DefineFn(nenv, "agl.I32.Sqrt", "func (i32) i32")
		e.DefineFn(nenv, "agl.I64.Sqrt", "func (i64) i64")
		e.DefineFn(nenv, "agl.Uint.Sqrt", "func (uint) uint")
		e.DefineFn(nenv, "agl.U8.Sqrt", "func (u8) u8")
		e.DefineFn(nenv, "agl.U16.Sqrt", "func (u16) u16")
		e.DefineFn(nenv, "agl.U32.Sqrt", "func (u32) u32")
		e.DefineFn(nenv, "agl.U64.Sqrt", "func (u64) u64")
		e.DefineFn(nenv, "agl.F32.Sqrt", "func (f32) f32")
		e.DefineFn(nenv, "agl.F64.Sqrt", "func (f64) f64")
		e.DefineFn(nenv, "agl.Int.Abs", "func (int) int")
		e.DefineFn(nenv, "agl.I8.Abs", "func (i8) i8")
		e.DefineFn(nenv, "agl.I16.Abs", "func (i16) i16")
		e.DefineFn(nenv, "agl.I32.Abs", "func (i32) i32")
		e.DefineFn(nenv, "agl.I64.Abs", "func (i64) i64")
		e.DefineFn(nenv, "agl.F32.Abs", "func (f32) f32")
		e.DefineFn(nenv, "agl.F64.Abs", "func (f64) f64")
		e.DefineFn(nenv, "agl.Int.String", "func (int) string")
		e.DefineFn(nenv, "agl.I64.String", "func (i64) string")
		e.DefineFn(nenv, "agl.I32.String", "func (i32) string")
		e.DefineFn(nenv, "agl.I16.String", "func (i16) string")
		e.DefineFn(nenv, "agl.I8.String", "func (i8) string")
		e.DefineFn(nenv, "agl.Uint.String", "func (uint) string")
		e.DefineFn(nenv, "agl.U8.String", "func (u8) string")
		e.DefineFn(nenv, "agl.U16.String", "func (u16) string")
		e.DefineFn(nenv, "agl.U32.String", "func (u32) string")
		e.DefineFn(nenv, "agl.U64.String", "func (u64) string")
		e.DefineFn(nenv, "agl.Vec.AllSatisfy", "func [T any](a []T, f func(T) bool) bool", WithDesc("Returns a Boolean value indicating whether every element of a sequence satisfies a given predicate."))
		e.DefineFn(nenv, "agl.Vec.Any", "func [T any](a []T, f func(T) bool) bool")
		e.DefineFn(nenv, "agl.Vec.Clear", "func [T any](mut a []T)")
		e.DefineFn(nenv, "agl.Vec.Clone", "func [T any](a []T) []T")
		e.DefineFn(nenv, "agl.Vec.Contains", "func [T comparable](a []T, e T) bool")
		e.DefineFn(nenv, "agl.Vec.ContainsWhere", "func [T comparable](a []T, where: p func(T) bool) bool")
		e.DefineFn(nenv, "agl.Vec.DropFirst", "func [T any](a []T, k int) []T")
		e.DefineFn(nenv, "agl.Vec.DropLast", "func [T any](a []T, k int) []T")
		e.DefineFn(nenv, "agl.Vec.Filter", "func [T any](a []T, f func(e T) bool) []T", WithDesc("Returns a new collection of the same type containing, in order, the elements of the original collection that satisfy the given predicate."))
		e.DefineFn(nenv, "agl.Vec.FilterMap", "func [T, R any](a []T, f func(e T) R?) []R")
		e.DefineFn(nenv, "agl.Vec.Find", "func [T any](a []T, f func(e T) bool) T?")
		e.DefineFn(nenv, "agl.Vec.FirstIndex", "func [T any](a []T, of: e T) int?")
		e.DefineFn(nenv, "agl.Vec.FirstIndexWhere", "func [T any](a []T, where: p func(T) bool) int?")
		e.DefineFn(nenv, "agl.Vec.Indices", "func [T any](a []T) []int")
		e.DefineFn(nenv, "agl.Vec.Insert", "func [T any](mut a []T, idx int, el T)")
		e.DefineFn(nenv, "agl.Vec.Iter", "func [T any](a []T) DoubleEndedExactSizeIterator[T]")
		e.DefineFn(nenv, "agl.Vec.Joined", "func (a []string) string", WithDesc("Returns the elements of this sequence of sequences, concatenated."))
		e.DefineFn(nenv, "agl.Vec.Map", "func [T, R any](a []T, f func(T) R) []R", WithDesc("Returns an array containing the results of mapping the given closure over the sequence’s elements."))
		e.DefineFn(nenv, "agl.Vec.Pop", "func [T any](mut a []T) T?")
		e.DefineFn(nenv, "agl.Vec.PopFront", "func [T any](mut a []T) T?")
		e.DefineFn(nenv, "agl.Vec.PopIf", "func [T any](mut a []T, pred func() bool) T?")
		e.DefineFn(nenv, "agl.Vec.Push", "func [T any](mut a []T, els ...T)")
		e.DefineFn(nenv, "agl.Vec.PushFront", "func [T any](mut a []T, el T)")
		e.DefineFn(nenv, "agl.Vec.Reduce", "func [T any, R cmp.Ordered](a []T, r R, f func(a R, e T) R) R", WithDesc("Returns the result of combining the elements of the sequence using the given closure."))
		e.DefineFn(nenv, "agl.Vec.ReduceInto", "func [T any, R cmp.Ordered](a []T, into: r R, f func(mut a *R, e T)) R", WithDesc("Returns the result of combining the elements of the sequence using the given closure."))
		e.DefineFn(nenv, "agl.Vec.Remove", "func [T any](mut a []T, i int)")
		e.DefineFn(nenv, "agl.Vec.RemoveFirst", "func [T any](mut a []T) T")
		e.DefineFn(nenv, "agl.Vec.Sorted", "func [E cmp.Ordered](a []E) []E", WithDesc("Returns the elements of the sequence, sorted."))
		e.DefineFn(nenv, "agl.Vec.SortedBy", "func [E any](a []E, f func(a, b E) bool) []E")
		e.DefineFn(nenv, "agl.Vec.Sum", "func [T cmp.Ordered](a []T) T")
		e.DefineFn(nenv, "agl.Vec.Swap", "func [T any](mut a []T, b, c Integer)")
		e.DefineFn(nenv, "agl.Vec.With", "func [T any](mut a []T, i int, clb func(*T))")
		e.DefineFn(nenv, "agl.Vec.__ADD", "func [T any](a, b []T) []T")
		e.DefineFn(nenv, "agl.Map.AllSatisfy", "func [K comparable, V any](m map[K]V, f func(DictEntry[K, V]) bool) bool")
		e.DefineFn(nenv, "agl.Map.ContainsKey", "func [K comparable, V any](m map[K]V, k K) bool")
		e.DefineFn(nenv, "agl.Map.Drain", "func [K comparable, V any](mut m map[K]V) Iterator[DictEntry[K, V]]")
		e.DefineFn(nenv, "agl.Map.Filter", "func [K comparable, V any](m map[K]V, f func(DictEntry[K, V]) bool) map[K]V")
		e.DefineFn(nenv, "agl.Map.Get", "func [K comparable, V any](m map[K]V) V?")
		e.DefineFn(nenv, "agl.Map.Insert", "func [K comparable, V any](mut m map[K]V, k K, v V) V?")
		e.DefineFn(nenv, "agl.Map.Iter", "func [K comparable, V any](m map[K]V) Iterator[DictEntry[K, V]]")
		e.DefineFn(nenv, "agl.Map.IsEmpty", "func [K comparable, V any](m map[K]V) bool")
		e.DefineFn(nenv, "agl.Map.Keys", "func [K comparable, V any](m map[K]V) Iterator[K]")
		e.DefineFn(nenv, "agl.Map.Len", "func [K comparable, V any](m map[K]V) int")
		e.DefineFn(nenv, "agl.Map.Map", "func [K comparable, V, R any](m map[K]V, f func(DictEntry[K, V]) R) []R")
		e.DefineFn(nenv, "agl.Map.Reduce", "func [K comparable, V, R any](m map[K]V, R, f func(R, DictEntry[K, V]) R) R")
		e.DefineFn(nenv, "agl.Map.ReduceInto", "func [K comparable, V, R any](m map[K]V, into: r R, f func(mut a *R, e DictEntry[K, V])) R")
		e.DefineFn(nenv, "agl.Map.Remove", "func [K comparable, V any](mut m map[K]V, k K) V?", WithDesc("Removes a key from the map, returning the value at the key if the key was previously in the map."))
		e.DefineFn(nenv, "agl.Map.Values", "func [K comparable, V any](m map[K]V) Iterator[V]")
		e.DefineFn(nenv, "agl.Option.IsNone", "func () bool")
		e.DefineFn(nenv, "agl.Option.IsSome", "func () bool")
		e.DefineFn(nenv, "agl.Option.Map", "func [T, R any](o T?, f func(e T) R) R?")
		e.DefineFn(nenv, "agl.Option.Unwrap", "func [T any]() T", WithDesc("Unwraps an Option value, yielding the content of a Some(x), or panic if None."))
		e.DefineFn(nenv, "agl.Option.UnwrapOr", "func [T any](t T) T", WithDesc("Unwraps an Option value, yielding the content of a Some(x), or a default if None."))
		e.DefineFn(nenv, "agl.Option.UnwrapOrDefault", "func [T any]() T", WithDesc("Unwraps an Option value, yielding the content of a Some(x), or the default if None."))
		e.DefineFn(nenv, "agl.Result.IsErr", "func () bool")
		e.DefineFn(nenv, "agl.Result.IsOk", "func () bool")
		e.DefineFn(nenv, "agl.Result.Unwrap", "func [T any]() T")
		e.DefineFn(nenv, "agl.Result.UnwrapOr", "func [T any](t T) T")
		e.DefineFn(nenv, "agl.Result.UnwrapOrDefault", "func [T any]() T")
		e.DefineFn(nenv, "Sequence.Contains", "func [T comparable](s Sequence[T], e T) bool")
		e.DefineFn(nenv, "Sequence.Filter", "func [T any](s Sequence[T], f func(e T) bool) Sequence[T]")
		e.DefineFn(nenv, "Sequence.FilterMap", "func [T, R any](s Sequence[T], f func(e T) R?) Sequence[R]")
		e.DefineFn(nenv, "Sequence.Joined", "func (s Sequence[string], sep string) string")
		e.DefineFn(nenv, "Sequence.Len", "func [T any](s Sequence[T]) int")
		e.DefineFn(nenv, "Sequence.Max", "func [T cmp.Ordered](s Sequence[T]) T?")
		e.DefineFn(nenv, "Sequence.Min", "func [T cmp.Ordered](s Sequence[T]) T?")
		e.DefineFn(nenv, "Sequence.Sorted", "func [T cmp.Ordered](s Sequence[T]) []T")
		e.DefineFn(nenv, "Sequence.Sum", "func [T, R Number](a Sequence[T]) R")
		e.DefineFn(nenv, "Sequence.TakeWhile", "func [T any](s Sequence[T], f func(T) bool) Sequence[T]")
		e.DefineFn(nenv, "Set", "func[T comparable] (it Iterator[T]) agl.Set[T]")
		e.DefineFn(nenv, "Array", "func[T any] (it Iterator[T]) []T")
		e.DefineFn(nenv, "agl.DoubleEndedIterator.AdvanceBackBy", "func[T any](it DoubleEndedIterator[T], n int) void!", WithDesc("Advances the iterator from the back by n elements."))
		e.DefineFn(nenv, "agl.DoubleEndedIterator.NextBack", "func[T any](it DoubleEndedIterator[T]) T?", WithDesc("Removes and returns an element from the end of the iterator."))
		e.DefineFn(nenv, "agl.DoubleEndedIterator.NthBack", "func[T any](it DoubleEndedIterator[T], n int) T?", WithDesc("Returns the nth element from the end of the iterator."))
		e.DefineFn(nenv, "agl.DoubleEndedIterator.Rev", "func[T any, I DoubleEndedIterator[T]]() *agl.Rev[T]", WithDesc("A double-ended iterator with the direction inverted."))
		e.DefineFn(nenv, "agl.DoubleEndedIterator.RFind", "func[T any](it DoubleEndedIterator[T], pred func(T) bool) T?", WithDesc("Searches for an element of an iterator from the back that satisfies a predicate."))
		e.DefineFn(nenv, "agl.ExactSizeIterator.Len", "func[T any](it ExactSizeIterator[T]) int", WithDesc("Returns the exact remaining length of the iterator."))
		e.DefineFn(nenv, "agl.ExactSizeIterator.IsEmpty", "func[T any](it ExactSizeIterator[T]) bool", WithDesc("Returns true if the iterator is empty."))
		e.DefineFn(nenv, "agl.Iterator.AdvanceBy", "func[T any](it Iterator[T], n int) void!")
		e.DefineFn(nenv, "agl.Iterator.AllSatisfy", "func[T any](it Iterator[T], pred func(T) bool) bool")
		e.DefineFn(nenv, "agl.Iterator.Any", "func [T any](it Iterator[T], f func(T) bool) bool")
		e.DefineFn(nenv, "agl.Iterator.Chain", "func[T any](it Iterator[T], b Iterator[T]) Iterator[T]")
		e.DefineFn(nenv, "agl.Iterator.Contains", "func[T any](it Iterator[T], el T) bool")
		e.DefineFn(nenv, "agl.Iterator.Count", "func [T any](it Iterator[T]) int")
		e.DefineFn(nenv, "agl.Iterator.Cycle", "func [T any](it CloneIterator[T]) Iterator[T]")
		e.DefineFn(nenv, "agl.Iterator.Filter", "func [T any](it Iterator[T], f func(T) bool) Iterator[T]")
		e.DefineFn(nenv, "agl.Iterator.FilterMap", "func [T, R any](s Iterator[T], f func(e T) R?) Iterator[R]")
		e.DefineFn(nenv, "agl.Iterator.ForEach", "func[T any](it Iterator[T], f func(T))")
		e.DefineFn(nenv, "agl.Iterator.Joined", "func (it Iterator[string], sep string) string")
		e.DefineFn(nenv, "agl.Iterator.Map", "func [T, R any](it Iterator[T], f func(T) R) Iterator[R]")
		e.DefineFn(nenv, "agl.Iterator.Max", "func [T cmp.Ordered](it Iterator[T]) T?")
		e.DefineFn(nenv, "agl.Iterator.Min", "func [T cmp.Ordered](it Iterator[T]) T?")
		e.DefineFn(nenv, "agl.Iterator.Next", "func [T any](it Iterator[T]) T?")
		e.DefineFn(nenv, "agl.Iterator.Nth", "func [T any](it Iterator[T], n int) T?")
		e.DefineFn(nenv, "agl.Iterator.Peekable", "func [T any](it Iterator[T]) *Peekable[T]")
		e.DefineFn(nenv, "agl.Iterator.Position", "func [T any](it Iterator[T], f func(T) bool) int?")
		e.DefineFn(nenv, "agl.Iterator.RPosition", "func [T any](it DoubleEndedExactSizeIterator[T], f func(T) bool) int?")
		e.DefineFn(nenv, "agl.Iterator.Skip", "func [T any](it Iterator[T], n int) Iterator[T]")
		e.DefineFn(nenv, "agl.Iterator.SkipWhile", "func [T any](it Iterator[T], f func(T) bool) Iterator[T]")
		e.DefineFn(nenv, "agl.Iterator.Sorted", "func [T cmp.Ordered](s Iterator[T]) []T")
		e.DefineFn(nenv, "agl.Iterator.StepBy", "func [T any](it Iterator[T], n int) Iterator[T]")
		e.DefineFn(nenv, "agl.Iterator.Sum", "func [T, R Number](it Iterator[T]) R")
		e.DefineFn(nenv, "agl.Iterator.Take", "func [T any](it Iterator[T], n int) Iterator[T]")
		e.DefineFn(nenv, "agl.Iterator.TakeWhile", "func [T any](it Iterator[T], f func(T) bool) Iterator[T]")
	})
}

func CoreFns() string {
	return string(Must(ContentFs.ReadFile(filepath.Join("core", "core.agl"))))
}

func (e *Env) loadBaseValues() {
	m := NewPkgVisited()
	e.loadCoreTypes()
	e.loadCoreFunctions(m)
	e.loadPkgAgl(m)
	e.Define(nil, "Option", types.OptionType{})
	e.Define(nil, "comparable", types.TypeType{W: types.CustomType{Name: "comparable", W: types.AnyType{}}})
}

func NewEnv(fset *token.FileSet) *Env {
	env := &Env{
		ID:          envIDCounter.Add(1),
		lookupTable: make(map[string]*Info),
		lspTable:    make(map[NodeKey]*Info),
		fset:        fset,
	}
	env.loadBaseValues()
	return env
}

func (e *Env) SubEnv() *Env {
	env := &Env{
		ID:          envIDCounter.Add(1),
		lookupTable: make(map[string]*Info),
		lspTable:    e.lspTable,
		parent:      e,
		NoIdxUnwrap: e.NoIdxUnwrap,
		fset:        e.fset,
		inferrer:    e.inferrer, // Propagate inferrer reference
	}
	//p("SubEnv", e.ID, env.ID)
	return env
}

func (e *Env) GetDirect(name string) types.Type {
	info := e.lookupTable[name]
	if info == nil {
		return nil
	}
	return info.Type
}

func (e *Env) Get(name string) types.Type {
	res := e.getHelper(name)
	if res == nil && e.parent != nil {
		return e.parent.Get(name)
	}
	return res
}

func (e *Env) getHelper(name string) types.Type {
	if el := e.GetNameInfo(name); el != nil {
		return el.Type
	}
	return nil
}

func (e *Env) GetNameInfoDirect(name string) *Info {
	return e.lookupTable[name]
}

func (e *Env) GetNameInfo(name string) *Info {
	info := e.lookupTable[name]
	if info == nil && e.parent != nil {
		return e.parent.GetNameInfo(name)
	}
	return info
}

func (e *Env) GetOrCreateNameInfo(name string) *Info {
	info := Or(e.GetNameInfoDirect(name), &Info{})
	e.lookupTable[name] = info
	return info
}

func (e *Env) GetFn(name string) types.FuncType {
	t := e.Get(name)
	// Unwrap wrappers but preserve type structure
	for {
		switch v := t.(type) {
		case types.TypeType:
			t = v.W
		case types.MutType:
			t = v.W
		case types.LabelledType:
			t = v.W
		case types.StarType:
			t = v.X
		default:
			return t.(types.FuncType)
		}
	}
}

func (e *Env) DefineFn(nenv *Env, name string, fnStr string, opts ...SetTypeOption) {
	fnT := parseFuncTypeFromString(name, fnStr, nenv, nil)
	e.Define(nil, name, fnT, opts...)
}

func (e *Env) DefineFnNative(name string, fnStr string, fset *token.FileSet) {
	fnT := parseFuncDeclFromStringHelper(name, fnStr, e, fset)
	e.Define(nil, name, fnT)
}

func (e *Env) DefineFnNative2(name string, fnT types.FuncType, opts ...SetTypeOption) error {
	return e.Define1(nil, name, fnT, opts...)
}

func (e *Env) DefinePkg(name, path string) error {
	return e.DefineErr(nil, name, types.PackageType{Name: name, Path: path})
}

func (e *Env) DefineForce(n ast.Node, name string, typ types.Type) {
	err := e.defineHelper(n, name, typ, true)
	if err != nil {
		assert(false, err.Error())
	}
}

func (e *Env) Define(n ast.Node, name string, typ types.Type, opts ...SetTypeOption) {
	if err := e.Define1(n, name, typ, opts...); err != nil {
		assert(false, err.Error())
	}
}

func (e *Env) Define1(n ast.Node, name string, typ types.Type, opts ...SetTypeOption) error {
	err := e.defineHelper(n, name, typ, false, opts...)
	if err != nil {
		return err
	}
	return nil
}

func (e *Env) DefineErr(n ast.Node, name string, typ types.Type) error {
	return e.defineHelper(n, name, typ, false)
}

type DefinitionProvider struct {
	URI             string
	Line, Character int
}

func (e *Env) defineHelper(n ast.Node, name string, typ types.Type, force bool, opts ...SetTypeOption) error {
	if name == "_" {
		return nil
	}
	//p("DEF", name, typ, e.ID)
	conf := &SetTypeConf{}
	for _, opt := range opts {
		opt(conf)
	}
	if !force {
		t := e.GetDirect(name)
		if t != nil {
			//p("?", name, t, e.ID)
			// If the name is already defined, don't overwrite it
			// This ensures that agl packages take precedence over std packages
			return nil
		}
	}
	info := e.GetOrCreateNameInfo(name)
	info.Type = typ
	if conf.definition1 != nil {
		info.Definition1 = *conf.definition1
	}
	if conf.description != "" {
		info.Message = conf.description
	}
	if n != nil {
		info.Definition = n.Pos()
		lookupInfo := e.lspNodeOrCreate(n)
		lookupInfo.Definition = n.Pos()
	}
	return nil
}

func (e *Env) lspNode(n ast.Node) *Info {
	return e.lspTable[e.makeKey(n)]
}

func (e *Env) lspNodeOrCreate(n ast.Node) *Info {
	info := Or(e.lspNode(n), &Info{})
	e.lspSetNode(n, info)
	return info
}

func (e *Env) lspSetNode(n ast.Node, info *Info) {
	e.lspTable[e.makeKey(n)] = info
}

func (e *Env) Assign(parentInfo *Info, n ast.Node, name string, fset *token.FileSet, mutEnforced bool) error {
	if name == "_" {
		return nil
	}
	t := e.Get(name)
	if t == nil {
		return fmt.Errorf("undeclared %s", name)
	}
	if mutEnforced && !TryCast[types.MutType](t) {
		return fmt.Errorf("cannot assign to immutable variable '%s'", name)
	}
	e.lspNodeOrCreate(n).Definition = parentInfo.Definition
	return nil
}

func (e *Env) SetType(p *Info, def1 *DefinitionProvider, x ast.Node, t types.Type, fset *token.FileSet) {
	assertf(t != nil, "%s: try to set type nil, %v %v", fset.Position(x.Pos()), x, to(x))
	info := e.lspNodeOrCreate(x)
	info.Type = t
	if def1 != nil {
		info.Definition1 = *def1
	}
	if p != nil {
		info.Message = p.Message
		info.Definition = p.Definition
	}
}

type NodeKey string

func makeKey(n ast.Node) NodeKey {
	return NodeKey(fmt.Sprintf("%d_%d_%v", n.Pos(), n.End(), reflect.TypeOf(n)))
}

func (e *Env) makeKey(x ast.Node) NodeKey {
	return makeKey(x)
}

func panicIfNil(t types.Type, typ any) {
	if t == nil {
		panic(fmt.Sprintf("type not found %v %v", typ, to(typ)))
	}
}

func (e *Env) GetInfo(x ast.Node) *Info {
	return e.lspNode(x)
}

func (e *Env) GetType(x ast.Node) types.Type {
	if v := e.lspNode(x); v != nil {
		return v.Type
	}
	return nil
}

func (e *Env) GetType2(x ast.Node, fset *token.FileSet) types.Type {
	res := e.getType2Helper(x, fset)
	if res == nil && e.parent != nil {
		return e.parent.GetType2(x, fset)
	}
	return res
}

func (e *Env) getType2Helper(x ast.Node, fset *token.FileSet) types.Type {
	if v := e.lspNode(x); v != nil {
		return v.Type
	}
	switch xx := x.(type) {
	case *ast.Ident:
		if v2 := e.GetNameInfo(xx.Name); v2 != nil {
			return v2.Type
		}
		if e.pkgName != "" {
			if v2 := e.GetNameInfo(e.pkgName + "." + xx.Name); v2 != nil {
				return v2.Type
			}
		}
		return nil
		//panic(fmt.Sprintf("%s: %v", e.fset.Position(xx.Pos()), xx.Name))
	case *ast.FuncType:
		return funcTypeToFuncType("", xx, e, fset, false)
	case *ast.FuncLit:
		return funcTypeToFuncType("", xx.Type, e, fset, false)
	case *ast.Ellipsis:
		return types.EllipsisType{Elt: e.GetType2(xx.Elt, fset)}
	case *ast.ArrayType:
		return types.ArrayType{Elt: e.GetType2(xx.Elt, fset)}
	case *ast.ResultExpr:
		return types.ResultType{W: e.GetType2(xx.X, fset)}
	case *ast.ErrExpr:
		return types.ResultType{W: e.GetType2(&ast.Ident{Name: "error"}, fset)}
	case *ast.OkExpr:
		return types.ResultType{W: e.GetType2(xx.X, fset)}
	case *ast.OptionExpr:
		return types.OptionType{W: e.GetType2(xx.X, fset)}
	case *ast.SomeExpr:
		return types.OptionType{W: e.GetType2(xx.X, fset)}
	case *ast.NoneExpr:
		return types.OptionType{W: nil}
	case *ast.CallExpr:
		if v, ok := xx.Fun.(*ast.Ident); ok {
			if v.Name == "make" {
				return e.GetType2(xx.Args[0], fset)
			}
		}
		t := e.GetType2(xx.Fun, fset)
		if v, ok := t.(types.TypeType); ok {
			return v.W
		}
		t = types.Unwrap(t)
		// Check for error type to avoid cascading panics
		if _, ok := t.(types.ErrorType); ok {
			return types.ErrorType{}
		}
		if t == nil {
			return types.ErrorType{}
		}
		ft, ok := t.(types.FuncType)
		if !ok {
			return types.ErrorType{}
		}
		return ft.Return
	case *ast.BasicLit:
		switch xx.Kind {
		case token.INT:
			return types.UntypedNumType{}
		case token.STRING:
			return types.UntypedStringType{}
		default:
			panic(fmt.Sprintf("%v", xx.Kind))
		}
	case *ast.SelectorExpr:
		base := e.GetType2(xx.X, fset)
		// Unwrap wrapper types but preserve StructType for method lookup
		for {
			switch v := base.(type) {
			case types.TypeType:
				base = v.W
			case types.LabelledType:
				base = v.W
			case types.MutType:
				base = v.W
			case types.StarType:
				base = v.X
			default:
				goto afterUnwrap2
			}
		}
	afterUnwrap2:
		// Check if base is nil (undefined variable)
		if base == nil {
			var varName string
			if ident, ok := xx.X.(*ast.Ident); ok {
				varName = ident.Name
			} else {
				varName = fmt.Sprintf("%v", xx.X)
			}
			errMsg := fmt.Sprintf("undefined variable '%s'", varName)
			if e.inferrer != nil {
				e.inferrer.AddError(xx.X, errMsg)
				return types.ErrorType{}
			}
			panic(fmt.Sprintf("%s: %s", e.fset.Position(xx.X.Pos()), errMsg))
		}
		switch v := base.(type) {
		case types.PackageType:
			name := fmt.Sprintf("%s.%s", v.Name, xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.InterfaceType:
			return e.GetType2(&ast.Ident{Name: v.NameStr()}, fset)
		case types.StructType:
			name := v.GetFieldName(xx.Sel.Name)
			methodType := e.GetType2(&ast.Ident{Name: name}, fset)
			if methodType == nil {
				// For type aliases, try looking up the method on the base type name
				if len(v.Fields) == 1 && v.Fields[0].Name == "__alias" {
					// This is a type alias, use just the struct name
					baseName := v.String1() + "." + xx.Sel.Name
					methodType = e.GetType2(&ast.Ident{Name: baseName}, fset)
				}
				if methodType == nil {
					// Method not found, report error
					errMsg := fmt.Sprintf("Unresolved reference '%s'", xx.Sel.Name)
					if e.inferrer != nil {
						e.inferrer.AddError(xx, errMsg)
						return types.ErrorType{}
					}
					return nil
				}
			}
			// If this is a method on a generic type, monomorphize it
			if fnType, ok := methodType.(types.FuncType); ok && len(v.TypeParams) > 0 {
				// Build a map from generic parameter names to concrete types
				// We need to match the receiver type in the method with the base type
				// For now, assume the type parameters match by position
				genMap := make(map[string]types.Type)
				if len(fnType.Params) > 0 {
					recvType := types.Unwrap(fnType.Params[0])
					if recvStruct, ok := recvType.(types.StructType); ok {
						// Match type parameters from receiver to base
						for i, p := range recvStruct.TypeParams {
							if gp, ok := p.(types.GenericType); ok && i < len(v.TypeParams) {
								genMap[gp.Name] = v.TypeParams[i]
							}
						}
					}
				}
				// Apply substitutions
				for name, typ := range genMap {
					methodType = fnType.ReplaceGenericParameter(name, typ)
					fnType = methodType.(types.FuncType)
				}
			}
			return methodType
		case types.TupleType:
			idx, err := strconv.Atoi(xx.Sel.Name)
			if err != nil {
				panic("")
			}
			return v.Elts[idx]
		case types.TypeAssertType:
			return v.X
		case types.ArrayType:
			name := fmt.Sprintf("agl.Vec.%s", xx.Sel.Name)
			methodType := e.GetType2(&ast.Ident{Name: name}, fset)
			if methodType == nil {
				errMsg := fmt.Sprintf("method '.%s' does not exist on array type", xx.Sel.Name)
				// Add a suggestion if it's a common mistake
				if xx.Sel.Name == "Join" {
					errMsg += "\n    did you mean '.Joined()' instead?"
				}
				// If we have an inferrer, collect the error; otherwise panic
				if e.inferrer != nil {
					e.inferrer.AddError(xx.Sel, errMsg)
					return types.ErrorType{} // Return error type to prevent further panics
				}
				panic(fmt.Sprintf("%s: %s", e.fset.Position(xx.Sel.Pos()), errMsg))
			}
			return methodType
		case types.SetType:
			name := fmt.Sprintf("agl.Set.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.OptionType:
			name := fmt.Sprintf("Option.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.EnumType:
			return v
		case types.CustomType:
			name := fmt.Sprintf("%s.%s.%s", v.Pkg, v.Name, xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.StringType:
			name := fmt.Sprintf("agl.String.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.I64Type:
			name := fmt.Sprintf("agl.I64.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.RangeType:
			// Range types are iterators, check for iterator methods
			if xx.Sel.Name == "Rev" {
				name := "agl.DoubleEndedIterator.Rev"
				return e.GetType2(&ast.Ident{Name: name}, fset)
			} else if xx.Sel.Name == "NthBack" {
				name := "agl.DoubleEndedIterator.NthBack"
				return e.GetType2(&ast.Ident{Name: name}, fset)
			}
			name := fmt.Sprintf("agl.Iterator.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.RangeInclusiveType:
			// Range types are iterators, check for iterator methods
			if xx.Sel.Name == "Rev" {
				name := "agl.DoubleEndedIterator.Rev"
				return e.GetType2(&ast.Ident{Name: name}, fset)
			} else if xx.Sel.Name == "NthBack" {
				name := "agl.DoubleEndedIterator.NthBack"
				return e.GetType2(&ast.Ident{Name: name}, fset)
			}
			name := fmt.Sprintf("agl.Iterator.%s", xx.Sel.Name)
			return e.GetType2(&ast.Ident{Name: name}, fset)
		case types.MapType:
			name := fmt.Sprintf("agl.Map.%s", xx.Sel.Name)
			methodType := e.GetType2(&ast.Ident{Name: name}, fset)
			// Check if method exists (nil or VoidType means it doesn't exist)
			_, isVoid := methodType.(types.VoidType)
			if methodType == nil || isVoid {
				errMsg := fmt.Sprintf("no method '.%s' for type map", xx.Sel.Name)
				// If we have an inferrer, collect the error; otherwise panic
				if e.inferrer != nil {
					e.inferrer.AddError(xx.Sel, errMsg)
					return types.ErrorType{} // Return error type to prevent further panics
				}
				panic(fmt.Sprintf("%s: %s", e.fset.Position(xx.Sel.Pos()), errMsg))
			}
			return methodType
		case types.VoidType:
			// VoidType shouldn't have methods
			errMsg := fmt.Sprintf("no method '.%s' for type void", xx.Sel.Name)
			if e.inferrer != nil {
				e.inferrer.AddError(xx.Sel, errMsg)
				return types.ErrorType{}
			}
			panic(fmt.Sprintf("%s: %s", e.fset.Position(xx.Sel.Pos()), errMsg))
		case types.ErrorType:
			// Propagate error type to avoid cascading panics
			return types.ErrorType{}
		default:
			//return nil
			panic(fmt.Sprintf("%s: %v %v", e.fset.Position(xx.X.Pos()), xx.X, reflect.TypeOf(base)))
		}
		return nil
	case *ast.IndexExpr:
		t := e.GetType2(xx.X, fset)
		if t == nil {
			return types.ErrorType{}
		}

		// Unwrap wrapper types but preserve the type structure (StructType, FuncType, etc.)
		for {
			switch v := t.(type) {
			case types.TypeType:
				t = v.W
			case types.LabelledType:
				t = v.W
			case types.MutType:
				t = v.W
			case types.StarType:
				t = v.X
			default:
				goto afterUnwrap
			}
		}
	afterUnwrap:

		if !e.NoIdxUnwrap {
			switch v := t.(type) {
			case types.ArrayType:
				return v.Elt
			}
		}

		// Get the index type
		indexType := e.GetType2(xx.Index, fset)

		switch v := t.(type) {
		case types.StructType:
			// For generic types like Sequence[V], instantiate with the index type
			if len(v.TypeParams) > 0 {
				t = v.Concrete([]types.Type{indexType})
			} else {
				t = v.RenameGenericParameter("T", xx.Index.(*ast.Ident).Name)
			}
		case types.FuncType:
			if len(v.TypeParams) > 0 {
				t = v.Concrete([]types.Type{indexType})
			} else {
				t = v.RenameGenericParameter("V", "T")
			}
		case types.MapType:
			t = v.V
		case types.EnumType:
		case types.SetType:
		case types.InterfaceType:
			if len(v.TypeParams) > 0 {
				t = v.Concrete([]types.Type{indexType})
			}
		case types.ArrayType:
		case types.ErrorType:
			return types.ErrorType{}
		default:
			panic(fmt.Sprintf("%s: %v %v", e.fset.Position(xx.X.Pos()), t, to(t)))
		}
		return t
	case *ast.ParenExpr:
		return e.GetType2(xx.X, fset)
	case *ast.VoidExpr:
		return types.VoidType{}
	case *ast.StarExpr:
		return types.StarType{X: e.GetType2(xx.X, fset)}
	case *ast.MapType:
		return types.MapType{K: e.GetType2(xx.Key, fset), V: e.GetType2(xx.Value, fset)}
	case *ast.SetType:
		return types.SetType{K: e.GetType2(xx.Key, fset)}
	case *ast.ChanType:
		return types.ChanType{W: e.GetType2(xx.Value, fset)}
	case *ast.TupleExpr:
		elts := make([]types.Type, len(xx.Values))
		for i, v := range xx.Values {
			elts[i] = e.GetType2(v, fset)
		}
		return types.TupleType{Elts: elts}
	case *ast.BinaryExpr:
		switch xx.Op {
		case token.EQL, token.NEQ, token.LOR, token.LAND, token.LEQ, token.LSS, token.GEQ, token.GTR, token.IN:
			return types.BoolType{}
		default:
			return e.GetType2(xx.X, fset)
		}
	case *ast.UnaryExpr:
		if xx.Op == token.SUB || xx.Op == token.ADD {
			if _, ok := xx.X.(*ast.BasicLit); ok {
				return e.GetType2(xx.X, fset)
			}
		}
		return types.UnaryType{X: e.GetType2(xx.X, fset)}
	case *ast.InterfaceType:
		return types.AnyType{}
	case *ast.CompositeLit:
		switch v := e.GetType2(xx.Type, fset).(type) {
		case types.CustomType:
			return types.StructType{Pkg: v.Pkg, Name: v.Name}
		case types.ArrayType:
			return e.GetType2(xx.Type, fset)
		case types.SetType:
			return e.GetType2(xx.Type, fset)
		case types.StructType:
			return types.StructType{Pkg: v.Pkg, Name: v.Name}
		case types.MapType:
			return e.GetType2(xx.Type, fset)
		case types.TypeType:
			return v.W
		default:
			//return nil
			panic(fmt.Sprintf("%s: %v %v", e.fset.Position(xx.Pos()), xx.Type, reflect.TypeOf(v)))
		}
	case *ast.TypeAssertExpr:
		xT := e.GetType2(xx.X, fset)
		var typeT types.Type
		if xx.Type != nil {
			typeT = e.GetType2(xx.Type, fset)
		}
		n := types.TypeAssertType{X: xT, Type: typeT}
		info := e.lspNodeOrCreate(xx)
		info.Type = types.OptionType{W: xT} // TODO ensure xT is the right thing
		return n
	case *ast.BubbleOptionExpr:
		return e.GetType2(xx.X, fset).(types.OptionType).W
	case *ast.BubbleResultExpr:
		return e.GetType2(xx.X, fset).(types.ResultType).W
	case *ast.SliceExpr:
		return e.GetType2(xx.X, fset) // TODO
	case *ast.IndexListExpr:
		return e.GetType2(xx.X, fset) // TODO
	case *ast.StructType:
		return types.StructType{}
	case *ast.LabelledArg:
		return e.GetType2(xx.X, fset)
	case *ast.IfExpr:
		return e.GetType2(xx.Body, fset)
	case *ast.BlockStmt:
		if len(xx.List) == 0 {
			return types.VoidType{}
		}
		return e.GetType2(xx.List[len(xx.List)-1], fset)
	case *ast.ExprStmt:
		return e.GetType2(xx.X, fset)
	case *ast.ReturnStmt:
		if xx.Result == nil {
			return types.VoidType{}
		}
		return e.GetType2(xx.Result, fset)
	case *ast.RangeExpr:
		sT := e.GetType2(xx.Start, fset)
		eT := e.GetType2(xx.End_, fset)
		t := sT
		if _, ok := t.(types.UntypedNumType); ok {
			t = eT
		}
		if xx.Op == token.RANGEOPEQ {
			return types.RangeInclusiveType{Typ: t}
		}
		return types.RangeType{Typ: t}
	default:
		//return nil
		panic(fmt.Sprintf("unhandled type %v %v", xx, reflect.TypeOf(xx)))
	}
}

func (e *Env) GetGoType2(pkgName string, x goast.Node, keepRaw bool) types.Type {
	res := e.getGoType2Helper(pkgName, x, keepRaw)
	if res == nil && e.parent != nil {
		return e.parent.GetGoType2(pkgName, x, keepRaw)
	}
	return res
}

func (e *Env) getGoType2Helper(pkgName string, x goast.Node, keepRaw bool) types.Type {
	switch xx := x.(type) {
	case *goast.Ident:
		if v2 := e.GetNameInfo(xx.Name); v2 != nil {
			return v2.Type
		}
		if v2 := e.GetNameInfo(pkgName + "." + xx.Name); v2 != nil {
			return v2.Type
		}
		return nil
	case *goast.FuncType:
		return goFuncTypeToFuncType("", pkgName, xx, e, true)
	case *goast.Ellipsis:
		t := e.GetGoType2(pkgName, xx.Elt, keepRaw)
		if t == nil {
			return nil
		}
		return types.EllipsisType{Elt: t}
	case *goast.ArrayType:
		t := e.GetGoType2(pkgName, xx.Elt, keepRaw)
		if t == nil {
			return nil
		}
		return types.ArrayType{Elt: t}
	case *goast.CallExpr:
		return nil
	case *goast.BasicLit:
		switch xx.Kind {
		case gotoken.INT:
			return types.UntypedNumType{}
		case gotoken.STRING:
			return types.UntypedStringType{}
		default:
			panic(fmt.Sprintf("%v", xx.Kind))
		}
	case *goast.SelectorExpr:
		base := e.GetGoType2(pkgName, xx.X, keepRaw)
		base = types.Unwrap(base)
		switch v := base.(type) {
		case types.PackageType:
			name := fmt.Sprintf("%s.%s", v.Name, xx.Sel.Name)
			return e.GetGoType2(pkgName, &goast.Ident{Name: name}, keepRaw)
		case types.InterfaceType:
			return e.GetGoType2(pkgName, &goast.Ident{Name: v.NameStr()}, keepRaw)
		case types.StructType:
			name := v.GetFieldName(xx.Sel.Name)
			return e.GetGoType2(pkgName, &goast.Ident{Name: name}, keepRaw)
		case types.TypeAssertType:
			return v.X
		default:
			//return types.VoidType{}
			panic(fmt.Sprintf("%v %v %v", reflect.TypeOf(base), xx, xx.X))
		}
		return nil
	case *goast.IndexExpr:
		t := e.GetGoType2(pkgName, xx.X, keepRaw)
		if !e.NoIdxUnwrap {
			switch v := t.(type) {
			case types.ArrayType:
				return v.Elt
			}
		}
		return t
	case *goast.ParenExpr:
		return e.GetGoType2(pkgName, xx.X, keepRaw)
	case *goast.StarExpr:
		return types.StarType{X: e.GetGoType2(pkgName, xx.X, keepRaw)}
	case *goast.MapType:
		return types.MapType{K: e.GetGoType2(pkgName, xx.Key, keepRaw), V: e.GetGoType2(pkgName, xx.Value, keepRaw)}
	case *goast.ChanType:
		return types.ChanType{W: e.GetGoType2(pkgName, xx.Value, keepRaw)}
	case *goast.BinaryExpr:
		return types.BinaryType{X: e.GetGoType2(pkgName, xx.X, keepRaw), Y: e.GetGoType2(pkgName, xx.Y, keepRaw)}
	case *goast.UnaryExpr:
		return types.UnaryType{X: e.GetGoType2(pkgName, xx.X, keepRaw)}
	case *goast.InterfaceType:
		return types.AnyType{}
	case *goast.CompositeLit:
		ct := e.GetGoType2(pkgName, xx.Type, keepRaw).(types.CustomType)
		return types.StructType{Pkg: ct.Pkg, Name: ct.Name}
	case *goast.SliceExpr:
		return e.GetGoType2(pkgName, xx.X, keepRaw) // TODO
	case *goast.IndexListExpr:
		return e.GetGoType2(pkgName, xx.X, keepRaw) // TODO
	case *goast.StructType:
		return types.StructType{}
	case *goast.Field:
		return e.GetGoType2(pkgName, xx.Type, keepRaw)
	default:
		panic(fmt.Sprintf("unhandled type %v %v", xx, reflect.TypeOf(xx)))
	}
}
